//Project 3 - CS 3451 - Labyrinth Lite
//Instructor: Jarek Rossignac
//Author: Tyler Powell
//Date: September 2011

boolean lineIntersection(pt a, pt b, pt c, pt d) { //determine if two lines intersect
  return (clockwise(a,b,c) != clockwise(a,b,d)) && (clockwise(c,d,a) != clockwise(c,d,b));
}

pt pointOfIntersection(pt a, pt b, pt c, pt d) { //determine the point of intersection of to lines
  float s = ((c.y - a.y)*(b.x-a.x) - (c.x-a.x)*(b.y-a.y))/((d.x-c.x)*(b.y-a.y)-(d.y-c.y)*(b.x-a.x));
  float u = (c.x + s*(d.x-c.x) - a.x)/(b.x-a.x);
  return new pt(a.x + u*(b.x-a.x), a.y + u*(b.y-a.y)); 
} 

boolean clockwise(pt a, pt b, pt c) { //determine if three points are oriented in a clockwise direction
  return cross(new vec(a,b), new vec(b,c)) > 0;
}

boolean parallel(pt a, pt b, pt c, pt d) { //determine if two lines are parallel
  return cross(new vec(a,b), new vec(c,d)) == 0;
}

boolean coparallel(pt a, pt b, pt c, pt d) {
  int equalPoints = 0;
  if(pointEquivalence(a,c)) {
    equalPoints++;
  }
  if(pointEquivalence(a,d)) {
    equalPoints++;
  }
  if(pointEquivalence(b,c)) {
    equalPoints++;
  }
  if(pointEquivalence(b,d)) {
    equalPoints++;
  }
  if(equalPoints >= 2) {
    return true;
  }
  return parallel(a,b,c,d) && (pointOnLineExclusive(a,c,d) || pointOnLineExclusive(b,c,d) || pointOnLineExclusive(c,a,b) || pointOnLineExclusive(d,a,b));
}

boolean pointInBounds(pt p, pt a, pt b){
  vec ap = new vec(a,p);
  vec ab = new vec(a,b);
  vec bp = new vec(b,p);
  vec ba = new vec(b,a);
  return dot(ap,ab)>0 && dot(bp,ba)>0;
}

boolean pointOnLine2(pt p , pt a, pt b){
  vec ap = new vec(a,p);
  vec ab = new vec(a,b);
  vec bp = new vec(b,p);
  vec ba = new vec(b,a);
  return cross(ap,ab) < 1 && cross(ap,ab) > -1 && cross(bp,ba) < 1 && cross(bp,ba) > -1 && pointInBounds(p,a,b);
}

boolean pointOnLineExclusive(pt p, pt a, pt b) { //is point p on line ab, not including points a and b
  return pointOnLine(p,a,b) && !pointEquivalence(p,a) && !pointEquivalence(p,b);
}

boolean pointOnLine(pt p, pt a, pt b) { // is point p on line ab
  
  return ((-(b.x-a.x)/(b.y-a.y)*(a.y - p.y) + a.x == p.x) ||(-(b.y-a.y)/(b.x-a.x)*(a.x - p.x) + a.y == p.y)) && pointInBounds(p,a,b);
}

boolean pointOnLineApprox(pt p, pt a, pt b) {
  
  return ((-(b.x-a.x)/(b.y-a.y)*(a.y - p.y) + a.x <= p.x+ballRadius) && (-(b.x-a.x)/(b.y-a.y)*(a.y - p.y) + a.x >= p.x-ballRadius) ||(-(b.y-a.y)/(b.x-a.x)*(a.x - p.x) + a.y <= p.y+ballRadius) && (-(b.y-a.y)/(b.x-a.x)*(a.x - p.x) + a.y >= p.y-ballRadius)) && pointInBounds(p,a,b);
}

boolean pointEquivalence(pt a, pt b) {
  return a.x == b.x && a.y == b.y;
}

pt pointFromLine(float x, pt a, pt b) { //get a point from a line with value x
  float ans = -(b.y-a.y)/(b.x-a.x)*(a.x - x) + a.y;
  return new pt(x,ans);
}

boolean wallValid(pt p) { //determine if a wall crosses another wall
  pt last = labPoints.get(labPoints.size()-1);
  for(int i = 0; i < labPoints.size()-1; i++) {
    pt a = labPoints.get(i);
    pt b = labPoints.get(i+1);
    if(lineIntersection(p,last,a,b) || coparallel(p,last,a,b)) {
      return false;
    }
  }
  return true;
}

boolean gameWon = false;
boolean collision(pt loc1, pt loc2) { //check for collisions
  vec btw = new vec(loc1,loc2);
  vec ubtw = U(btw);
  if(d(loc1,loc2)<=ballRadius/2){
    loc2.x = 2*ubtw.x+loc1.x;
    loc2.y = 2*ubtw.y+loc1.y;
  }
  loc1.x = loc1.x-ubtw.x;
  loc1.y = loc1.y-ubtw.y;
  
  if(!labPoints.isEmpty() ){
    pt first = labPoints.get(0);
    pt last = labPoints.get(labPoints.size()-1);
    if(lineIntersection(loc1,loc2,first,last) || coparallel(loc1,loc2,first,last)){
      gameWon = true;
      return true;
    }
  }
  for(int i = 0; i < labPoints.size()-1; i++) {
    
    pt a = labPoints.get(i);
    pt b = labPoints.get(i+1);
    pt p = pointOfIntersection(loc1,loc2,a,b);
    vec u = U(loc1, p);
    vec n = u.left();
    pt closestEdge1 = new pt(loc1.x,loc1.y);
    closestEdge1 = T(closestEdge1,ballRadius/2,n);
    pt closestEdge2 = new pt(loc2.x,loc2.y);
    closestEdge2 = T(closestEdge2,ballRadius/2,n);
    pt p2 = pointOfIntersection(closestEdge1,closestEdge2,a,b);
    
//    a = new pt(a.x-u.x*ballRadius/2,a.y-u.y*ballRadius/2);
//    b = new pt(b.x-u.x*ballRadius/2,b.y-u.y*ballRadius/2);
    vec lineU = U(new vec(a,b));
    vec lineN = lineU.left().scaleBy(-1);
    vec lineNScale = new vec(lineN.x*ballRadius/2, lineN.y*ballRadius/2);
    a = T(a,lineNScale);
    b = T(b,lineNScale);

    
    if(threeD){
      lineNScale= new vec(lineN.x*wallThickness/2, lineN.y*wallThickness/2);
      a = T(a,lineNScale);
      b = T(b,lineNScale);
    }
    p = pointOfIntersection(loc1,loc2,a,b);
    line(a,b);
    line(loc1,loc2);
    p2 = T(p,lineN);
    
    
    pt inter = pointFromLine(ballLocation.x,a,b);
//    println(inter.x + " " +  inter.y);
//    println(d(inter,ballLocation));
//    if(pointInBounds(p2,a,b)){
//      println(pointInBounds(p2,a,b));
//    }
//      if(!threeD){
//        stroke(0);
//        ellipse(p.x,p.y,10,10);
//        noStroke();
//      }
    if(lineIntersection(loc1,loc2,a,b) || coparallel(loc1,loc2,a,b) || pointEquivalence(p2,a) || pointEquivalence(p2,b)/*|| (d(p2,ballLocation)<=ballRadius/2 && pointInBounds(p2,a,b))*/) { //|| (d(inter,ballLocation)<.5) && ((p.x >= a.x && p.x <= b.x) || (p.x <= a.x && p.x >= b.x)) && ((p.y >= a.y && p.y <= b.y) || (p.y <= a.y && p.y >= b.y))
      float colTime = timeAt(p,currentVelocity,ballLocation);

      ballLocation = p2;
      currentVelocity = elasticShock(currentVelocity,a,b);
//      if(currentVelocity.x<1 || currentVelocity.y<1){
//        currentVelocity.x += lineN.x/abs(lineN.x+.000000001);
//        currentVelocity.y += lineN.y/abs(lineN.y+.000000001);
//      }
      return true;
//      println("Time " + time);
//      println("Col Time " + colTime);
//      if(colTime > time-.01 && colTime < time+.01){
//        
//        currentVelocity = elasticShock(currentVelocity,p,a,b);
//        return true;
//      }
//        pt ballEdge = new pt(ballLocation.x+u.x*ballRadius,ballLocation.y+u.y*ballRadius);
//        if(pointOnLineApprox(ballEdge,a,b) || (abs(p.x-ballLocation.x) <= 1 && abs(p.y-ballLocation.y) <= 1))
//        {
////          ballLocation.x -= ballRadius*u.x;
////          ballLocation.y -= ballRadius*u.y;
//          currentVelocity = elasticShock(currentVelocity,p,a,b);
//          return true;
//        }
    }
  }
  return false;
}

vec elasticShock(vec velocity, pt collision) {
  vec n = U(ballLocation,collision);
  vec w = velocity.add(-1, n.scaleBy(2*c(velocity,n)));
  return w;
}

vec elasticShock(vec velocity, pt a, pt b) { //get the new velocity of the ball after a collision
   vec ab = new vec(a,b);
   vec t = ab.scaleBy(dot(velocity,ab)/dot(ab,ab));
   vec n = ab.left().scaleBy(dot(velocity,ab.left())/dot(ab.left(),ab.left()));
   return t.add(-1,n);
}


